using System;
using System.Linq;
using Microsoft.Isam.Esent.Interop;
using System.Runtime.Serialization;
using System.Diagnostics;

namespace Microsoft.Isam.Esent.Serialization.Attributes
{
	/// <summary>A fixed or variable length, raw binary column can be up to 255 bytes in length.</summary>
	/// <remarks>
	/// <para>Must be applied to a field/property of type '<see cref="ByteArray" />' or byte[].</para>
	/// <para>The underlying ESENT column type is JET_coltypBinary.</para>
	/// </remarks>
	public class EseShortBinaryAttribute : EseColumnAttrubuteBase
	{
		/// <summary>Initialize with the default column name, which is the name of the field/property.</summary>
		public EseShortBinaryAttribute() { }
		/// <summary>Initialize with non-default column name.</summary>
		public EseShortBinaryAttribute( string _columnName ) : base( _columnName ) { }

		/// <summary>Is always true.</summary>
		public override bool bFieldNullable { get { return true; } }

		/// <summary>The maximum length, in bytes, of a column.</summary>
		public int maxBytes = 255;

		/// <summary>If set to true, it will always use the same amount of space in a row,
		/// regardless of how much data is being stored in the column.</summary>
		/// <remarks>The default is false.</remarks>
		public bool bFixedLength = false;

		/// <summary>Get column definition.</summary>
		public override JET_COLUMNDEF getColumnDef()
		{
			JET_COLUMNDEF res = new JET_COLUMNDEF();
			res.coltyp = JET_coltyp.Binary;
			res.cbMax = maxBytes;
			if( maxBytes > 255 )
				throw new SerializationException( "Raw binary column that can be up to 255 bytes in length." );
			if( bFixedLength )
				res.grbit |= ColumndefGrbit.ColumnFixed;
			return res;
		}

		Func<object, byte[]> fnSave;
		Func<byte[], object> fnLoad;

		/// <summary>Throw an exception if this attribute is not compatible with the type of the field/property.</summary>
		/// <param name="t">The type of the field/property this attribute is applied.</param>
		public override void verifyTypeSupport( Type t )
		{
			Type tByteArray = typeof( ByteArray );
			if( tByteArray.Equals( t ) )
			{
				fnSave = value =>
				{
					ByteArray val = (ByteArray)value;
					if( val.isEmpty() )
						return null;
					return val.data;
				};
				fnLoad = bytes => new ByteArray( bytes );
				return;
			}
			if( typeof( byte[] ).Equals( t ) )
			{
				fnSave = value => (byte[])value;
				fnLoad = bytes => bytes;
				return;
			}
			throw new SerializationException( "The type must be 'ByteArray'" );
		}

		/// <summary>Store the column value in the database.</summary>
		public override void Serialize( EseCursorBase cur, JET_COLUMNID idColumn, object value, bool bNewRecord )
		{
			Api.SetColumn( cur.idSession, cur.idTable, idColumn, fnSave( value ) );
		}

		/// <summary>Retrieve the column value from the DB.</summary>
		public override object Deserialize( EseCursorBase cur, JET_COLUMNID idColumn )
		{
			byte[] bytes = Api.RetrieveColumn( cur.idSession, cur.idTable, idColumn );
			return fnLoad( bytes );
		}

		/// <summary>Make the search key for this column.</summary>
		public override void MakeKey( EseCursorBase cur, object val, MakeKeyGrbit flags )
		{
			if( val == null )
				Api.MakeKey( cur.idSession, cur.idTable, null, flags );
			else if( val is ByteArray )
				Api.MakeKey( cur.idSession, cur.idTable, ( (ByteArray)val ).data, flags );
			else if( val is byte[] )
				Api.MakeKey( cur.idSession, cur.idTable, (byte[])val, flags );
			else
				makeKeyException( val );
		}
	}
}